home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / ibmcom.arc / IBMCOM.PAS next >
Pascal/Delphi Source File  |  1991-04-28  |  18KB  |  685 lines

  1. UNIT ibmcom;
  2.  
  3. {Version 3.0}
  4.  
  5. {This unit is the communications port interrupt driver for the IBM-PC.
  6. It handles handles all low-level i/o through the serial port.  It is
  7. installed by calling com_install.  It deinstalls itself automatically
  8. when the program exits, or you can deinstall it by calling com_deinstall.
  9.  
  10. Donated to the public domain by Wayne E. Conrad, January, 1989.
  11. If you have any problems or suggestions, please contact me at my BBS:
  12.  
  13.     Pascalaholics Anonymous
  14.     (602) 484-9356
  15.     2400 bps
  16.     The home of WBBS
  17.     Lots of source code
  18. }
  19.  
  20.  
  21. INTERFACE
  22.  
  23. USES
  24.   Dos;
  25.  
  26.  
  27. TYPE
  28.   com_parity = (com_none, com_even, com_odd, com_zero, com_one);
  29.  
  30.  
  31. PROCEDURE com_flush_rx;
  32. PROCEDURE com_flush_tx;
  33. FUNCTION  com_carrier: Boolean;
  34. FUNCTION  com_rx: Char;
  35. FUNCTION  com_tx_ready: Boolean;
  36. FUNCTION  com_tx_empty: Boolean;
  37. FUNCTION  com_rx_empty: Boolean;
  38. PROCEDURE com_tx (ch: Char);
  39. PROCEDURE com_tx_string (st: String);
  40. PROCEDURE com_lower_dtr;
  41. PROCEDURE com_raise_dtr;
  42. PROCEDURE com_set_speed (speed: Word);
  43. PROCEDURE com_set_parity (parity: com_parity; stop_bits: Byte);
  44. PROCEDURE com_install
  45.   (
  46.   portnum  : Word;
  47.   VAR error: Word
  48.   );
  49. PROCEDURE com_deinstall;
  50.  
  51.  
  52. IMPLEMENTATION
  53.  
  54.  
  55. {Summary of IBM-PC Asynchronous Adapter Registers.  From:
  56.   Compute!'s Mapping the IBM PC and PCjr, by Russ Davis
  57.   (Greensboro, North Carolina, 1985: COMPUTE! Publications, Inc.),
  58.   pp. 290-292.
  59.  
  60. Addresses given are for COM1 and COM2, respectively.  The names given
  61. in parentheses are the names used in this module.
  62.  
  63.  
  64. 3F8/2F8 (uart_data) Read: transmit buffer.  Write: receive buffer, or baud
  65. rate divisor LSB if port 3FB, bit 7 = 1.
  66.  
  67. 3F9/2F9 (uart_ier) Write: Interrupt enable register or baud rate divisor
  68. MSB if port 3FB, bit 7 = 1.
  69. PCjr baud rate divisor is different from other models;
  70. clock input is 1.7895 megahertz rather than 1.8432 megahertz.
  71. Interrupt enable register:
  72.     bits 7-4  forced to 0
  73.     bit 3     1=enable change-in-modem-status interrupt
  74.     bit 2     1=enable line-status interrupt
  75.     bit 1     1=enable transmit-register-empty interrupt
  76.     bit 0     1=data-available interrupt
  77.  
  78. 3FA/2FA (uart_iir) Interrupt identification register (prioritized)
  79.      bits 7-3  forced to 0
  80.      bits 2-1  00=change-in-modem-status (lowest)
  81.      bits 2-1  01=transmit-register-empty (low)
  82.      bits 2-1  10=data-available (high)
  83.      bits 2-1  11=line status (highest)
  84.      bit 0     1=no interrupt pending
  85.      bit 0     0=interrupt pending
  86.  
  87. 3FB/2FB (uart_lcr) Line control register
  88.      bit 7  0=normal, 1=address baud rate divisor registers
  89.      bit 6  0=break disabled, 1=enabled
  90.      bit 5  0=don't force parity
  91.             1=if bit 4-3=01 parity always 1
  92.               if bit 4-3=11 parity always 0
  93.               if bit 3=0 no parity
  94.      bit 4  0=odd parity,1=even
  95.      bit 3  0=no parity,1=parity
  96.      bit 2  0=1 stop bit
  97.             1=1.5 stop bits if 5 bits/character or
  98.               2 stop bits if 6-8 bits/character
  99.      bits 1-0  00=5 bits/character
  100.                01=6 bits/character
  101.                10=7 bits/character
  102.                11=8 bits/character
  103.  
  104.      bits 5..3: 000 No parity
  105.                 001 Odd parity
  106.                 010 No parity
  107.                 011 Even parity
  108.                 100 No parity
  109.                 101 Parity always 1
  110.                 110 No parity
  111.                 111 Parity always 0
  112.  
  113.  
  114. 3FC/2FC (uart_mcr) Modem control register
  115.      bits 7-5  forced to zero
  116.      bit 4     0=normal, 1=loop back test
  117.      bits 3-2  all PCs except PCjr
  118.      bit 3     1=interrupts to system bus, user-designated output: OUT2
  119.      bit 2     user-designated output, OUT1
  120.      bit 1     1=activate rts
  121.      bit 0     1=activate dtr
  122.  
  123. 3FD/2FD (uart_lsr) Line status register
  124.      bit 7  forced to 0
  125.      bit 6  1=transmit shift register is empty
  126.      bit 5  1=transmit hold register is empty
  127.      bit 4  1=break received
  128.      bit 3  1=framing error received
  129.      bit 2  1=parity error received
  130.      bit 1  1=overrun error received
  131.      bit 0  1=data received
  132.  
  133. 3FE/2FE (uart_msr) Modem status register
  134.      bit 7  1=receive line signal detect
  135.      bit 6  1=ring indicator (all PCs except PCjr)
  136.      bit 5  1=dsr
  137.      bit 4  1=cts
  138.      bit 3  1=receive line signal detect has changed state
  139.      bit 2  1=ring indicator has changed state (all PCs except PCjr)
  140.      bit 1  1=dsr has changed state
  141.      bit 0  1=cts has changed state
  142.  
  143. 3FF/2FF (uart_spr) Scratch pad register.}
  144.  
  145.  
  146. {Maximum port number (minimum is 1) }
  147.  
  148. CONST
  149.   max_port = 4;
  150.  
  151.  
  152. {Base i/o address for each COM port}
  153.  
  154. CONST
  155.   uart_base: ARRAY [1..max_port] OF Integer = ($3F8, $2F8, $3E8, $2E8);
  156.  
  157.  
  158. {Interrupt numbers for each COM port}
  159.  
  160. CONST
  161.   intnums: ARRAY [1..max_port] OF Byte = ($0C, $0B, $0C, $0B);
  162.  
  163.  
  164. {i8259 interrupt levels for each port}
  165.  
  166. CONST
  167.   i8259levels: ARRAY [1..max_port] OF Byte = (4, 3, 4, 3);
  168.  
  169.  
  170. {This variable is TRUE if the interrupt driver has been installed, or FALSE
  171. if it hasn't.  It's used to prevent installing twice or deinstalling when not
  172. installed.}
  173.  
  174. CONST
  175.   com_installed: Boolean = False;
  176.  
  177.  
  178. {UART i/o addresses.  Values depend upon which COMM port is selected.}
  179.  
  180. VAR
  181.   uart_data: Word;             {Data register}
  182.   uart_ier : Word;             {Interrupt enable register}
  183.   uart_iir : Word;             {Interrupt identification register}
  184.   uart_lcr : Word;             {Line control register}
  185.   uart_mcr : Word;             {Modem control register}
  186.   uart_lsr : Word;             {Line status register}
  187.   uart_msr : Word;             {Modem status register}
  188.   uart_spr : Word;             {Scratch pad register}
  189.  
  190.  
  191. {Original contents of IER and MCR registers.  Used to restore UART
  192. to whatever state it was in before this driver was loaded.}
  193.  
  194. VAR
  195.   old_ier: Byte;
  196.   old_mcr: Byte;
  197.  
  198.  
  199. {Original contents of interrupt vector.  Used to restore the vector when
  200. the interrupt driver is deinstalled.}
  201.  
  202. VAR
  203.   old_vector: Pointer;
  204.  
  205.  
  206. {Original contents of interrupt controller mask.  Used to restore the
  207. bit pertaining to the comm controller we're using.}
  208.  
  209. VAR
  210.   old_i8259_mask: Byte;
  211.  
  212.  
  213. {Bit mask for i8259 interrupt controller}
  214.  
  215. VAR
  216.   i8259bit: Byte;
  217.  
  218.  
  219. {Interrupt vector number}
  220.  
  221. VAR
  222.   intnum: Byte;
  223.  
  224.  
  225. {Receive queue.  Received characters are held here until retrieved by
  226. com_rx.}
  227.  
  228. CONST
  229.   rx_queue_size = 128;   {Change to suit}
  230. VAR
  231.   rx_queue: ARRAY [1..rx_queue_size] OF Byte;
  232.   rx_in   : Word;        {Index of where to store next character}
  233.   rx_out  : Word;        {Index of where to retrieve next character}
  234.   rx_chars: Word;        {Number of chars in queue}
  235.  
  236.  
  237. {Transmit queue.  Characters to be transmitted are held here until the
  238. UART is ready to transmit them.}
  239.  
  240. CONST
  241.   tx_queue_size = 16;    {Change to suit}
  242. VAR
  243.   tx_queue: ARRAY [1..tx_queue_size] OF Byte;
  244.   tx_in   : Integer;     {Index of where to store next character}
  245.   tx_out  : Integer;     {Index of where to retrieve next character}
  246.   tx_chars: integer;     {Number of chars in queue}
  247.  
  248.  
  249. {This variable is used to save the next link in the "exit procedure" chain.}
  250.  
  251. VAR
  252.   exit_save: Pointer;
  253.  
  254.  
  255. {$I ints.inc}   {Macros for enabling and disabling interrupts}
  256.  
  257.  
  258. {Interrupt driver.  The UART is programmed to cause an interrupt whenever
  259. a character has been received or when the UART is ready to transmit another
  260. character.}
  261.  
  262. {$R-,S-}
  263. PROCEDURE com_interrupt_driver; INTERRUPT;
  264.  
  265. VAR
  266.   ch   : Char;
  267.   iir  : Byte;
  268.   dummy: Byte;
  269.  
  270. BEGIN
  271.  
  272.   {While bit 0 of the interrupt identification register is 0, there is an
  273.   interrupt to process}
  274.  
  275.   iir := Port [uart_iir];
  276.  
  277.   WHILE NOT Odd (iir) DO
  278.     BEGIN
  279.  
  280.     CASE iir SHR 1 OF
  281.  
  282.       {iir = 100b: Received data available.  Get the character, and if
  283.       the buffer isn't full, then save it.  If the buffer is full,
  284.       then ignore it.}
  285.  
  286.       2:
  287.         BEGIN
  288.         ch := Char (Port [uart_data] );
  289.         IF (rx_chars <= rx_queue_size) THEN
  290.           BEGIN
  291.           rx_queue [rx_in] := Ord (ch);
  292.           Inc (rx_in);
  293.           IF rx_in > rx_queue_size THEN
  294.             rx_in := 1;
  295.           rx_chars := Succ (rx_chars);
  296.           END;
  297.         END;
  298.  
  299.       {iir = 010b: Transmit register empty.  If the transmit buffer
  300.       is empty, then disable the transmitter to prevent any more
  301.       transmit interrupts.  Otherwise, send the character.
  302.  
  303.       The test of the line-status-register is to see if the transmit
  304.       holding register is truly empty.  Some UARTS seem to cause transmit
  305.       interrupts when the holding register isn't empty, causing transmitted
  306.       characters to be lost.}
  307.  
  308.       1:
  309.         IF (tx_chars <= 0) THEN
  310.           Port [uart_ier] := Port [uart_ier] AND NOT 2
  311.         ELSE
  312.           IF Odd (Port [uart_lsr] SHR 5) THEN
  313.             BEGIN
  314.             Port [uart_data] := tx_queue [tx_out];
  315.             Inc (tx_out);
  316.             IF tx_out > tx_queue_size THEN
  317.               tx_out := 1;
  318.             Dec (tx_chars);
  319.             END;
  320.  
  321.       {iir = 001b: Change in modem status.  We don't expect this interrupt,
  322.       but if one ever occurs we need to read the line status to reset it
  323.       and prevent an endless loop.}
  324.  
  325.       0:
  326.         dummy := Port [uart_msr];
  327.  
  328.       {iir = 111b: Change in line status.  We don't expect this interrupt,
  329.       but if one ever occurs we need to read the line status to reset it
  330.       and prevent an endless loop.}
  331.  
  332.       3:
  333.         dummy := Port [uart_lsr];
  334.  
  335.       END;
  336.  
  337.     iir := Port [uart_iir];
  338.     END;
  339.  
  340.   {Tell the interrupt controller that we're done with this interrupt}
  341.  
  342.   Port [$20] := $20;
  343.  
  344. END;
  345. {$R+,S+}
  346.  
  347.  
  348. {Flush (empty) the receive buffer.}
  349.  
  350. PROCEDURE com_flush_rx;
  351. BEGIN
  352.   disable_interrupts;
  353.   rx_chars := 0;
  354.   rx_in    := 1;
  355.   rx_out   := 1;
  356.   enable_interrupts;
  357. END;
  358.  
  359.  
  360. {Flush (empty) transmit buffer.}
  361.  
  362. PROCEDURE com_flush_tx;
  363. BEGIN
  364.   disable_interrupts;
  365.   tx_chars := 0;
  366.   tx_in    := 1;
  367.   tx_out   := 1;
  368.   enable_interrupts;
  369. END;
  370.  
  371.  
  372. {This function returns TRUE if a carrier is present.}
  373.  
  374. FUNCTION com_carrier: Boolean;
  375. BEGIN
  376.   com_carrier := com_installed AND Odd (Port [uart_msr] SHR 7);
  377. END;
  378.  
  379.  
  380. {Get a character from the receive buffer.  If the buffer is empty, return
  381. a NULL (#0).}
  382.  
  383. FUNCTION com_rx: Char;
  384. BEGIN
  385.   IF NOT com_installed OR (rx_chars = 0) THEN
  386.     com_rx := #0
  387.   ELSE
  388.     BEGIN
  389.     disable_interrupts;
  390.     com_rx := Chr (rx_queue [rx_out] );
  391.     Inc (rx_out);
  392.     IF rx_out > rx_queue_size THEN
  393.       rx_out := 1;
  394.     Dec (rx_chars);
  395.     enable_interrupts;
  396.     END;
  397. END;
  398.  
  399.  
  400. {This function returns True if com_tx can accept a character.}
  401.  
  402. FUNCTION com_tx_ready: Boolean;
  403. BEGIN
  404.   com_tx_ready := (tx_chars < tx_queue_size) OR NOT com_installed;
  405. END;
  406.  
  407.  
  408. {This function returns True if the transmit buffer is empty.}
  409.  
  410. FUNCTION com_tx_empty: Boolean;
  411. BEGIN
  412.   com_tx_empty := (tx_chars = 0) OR NOT com_installed;
  413. END;
  414.  
  415.  
  416. {This function returns True if the receive buffer is empty.}
  417.  
  418. FUNCTION com_rx_empty: Boolean;
  419. BEGIN
  420.   com_rx_empty := (rx_chars = 0) OR NOT com_installed;
  421. END;
  422.  
  423.  
  424. {Send a character.  Waits until the transmit buffer isn't full, then puts
  425. the character into it.  The interrupt driver will send the character
  426. once the character is at the head of the transmit queue and a transmit
  427. interrupt occurs.}
  428.  
  429. PROCEDURE com_tx (ch: Char);
  430. BEGIN
  431.   IF com_installed THEN
  432.     BEGIN
  433.     REPEAT UNTIL com_tx_ready;
  434.     disable_interrupts;
  435.     tx_queue [tx_in] := Ord (ch);
  436.     IF tx_in < tx_queue_size THEN
  437.       Inc (tx_in)
  438.     ELSE
  439.       tx_in := 1;
  440.     Inc (tx_chars);
  441.     Port [uart_ier] := Port [uart_ier] OR 2;
  442.     enable_interrupts;
  443.     END;
  444. END;
  445.  
  446.  
  447. {Send a whole string}
  448.  
  449. PROCEDURE com_tx_string (st: String);
  450. VAR
  451.   i: Byte;
  452. BEGIN
  453.   FOR i := 1 TO Length (st) DO
  454.     com_tx (st [i] );
  455. END;
  456.  
  457.  
  458. {Lower (deactivate) the DTR line.  Causes most modems to hang up.}
  459.  
  460. PROCEDURE com_lower_dtr;
  461. BEGIN
  462.   IF com_installed THEN
  463.     BEGIN
  464.     disable_interrupts;
  465.     Port [uart_mcr] := Port [uart_mcr] AND NOT 1;
  466.     enable_interrupts;
  467.     END;
  468. END;
  469.  
  470.  
  471. {Raise (activate) the DTR line.}
  472.  
  473. PROCEDURE com_raise_dtr;
  474. BEGIN
  475.   IF com_installed THEN
  476.     BEGIN
  477.     disable_interrupts;
  478.     Port [uart_mcr] := Port [uart_mcr] OR 1;
  479.     enable_interrupts;
  480.     END;
  481. END;
  482.  
  483.  
  484. {Set the baud rate.  Accepts any speed between 2 and 65535.  However,
  485. I am not sure that extremely high speeds (those above 19200) will
  486. always work, since the baud rate divisor will be six or less, where a
  487. difference of one can represent a difference in baud rate of
  488. 3840 bits per second or more.}
  489.  
  490. PROCEDURE com_set_speed (speed: Word);
  491. VAR
  492.   divisor: Word;
  493. BEGIN
  494.   IF com_installed THEN
  495.     BEGIN
  496.     IF speed < 2 THEN speed := 2;
  497.     divisor := 115200 DIV speed;
  498.     disable_interrupts;
  499.     Port  [uart_lcr]  := Port [uart_lcr] OR $80;
  500.     Portw [uart_data] := divisor;
  501.     Port  [uart_lcr]  := Port [uart_lcr] AND NOT $80;
  502.     enable_interrupts;
  503.     END;
  504. END;
  505.  
  506.  
  507. {Set the parity and stop bits as follows:
  508.  
  509.   com_none    8 data bits, no parity
  510.   com_even    7 data bits, even parity
  511.   com_odd     7 data bits, odd parity
  512.   com_zero    7 data bits, parity always zero
  513.   com_one     7 data bits, parity always one}
  514.  
  515. PROCEDURE com_set_parity (parity: com_parity; stop_bits: Byte);
  516. VAR
  517.   lcr: Byte;
  518. BEGIN
  519.   CASE parity OF
  520.     com_none: lcr := $00 OR $03;
  521.     com_even: lcr := $18 OR $02;
  522.     com_odd : lcr := $08 OR $02;
  523.     com_zero: lcr := $38 OR $02;
  524.     com_one : lcr := $28 OR $02;
  525.     END;
  526.   IF stop_bits = 2 THEN
  527.     lcr := lcr OR $04;
  528.   disable_interrupts;
  529.   Port [uart_lcr] := Port [uart_lcr] AND $40 OR lcr;
  530.   enable_interrupts;
  531. END;
  532.  
  533. {Install the communications driver.  Portnum should be 1..max_port.
  534. Error codes returned are:
  535.  
  536.   0 - No error
  537.   1 - Invalid port number
  538.   2 - UART for that port is not present
  539.   3 - Already installed, new installation ignored}
  540.  
  541. PROCEDURE com_install
  542.   (
  543.   portnum  : Word;
  544.   VAR error: Word
  545.   );
  546. VAR
  547.   ier: Byte;
  548. BEGIN
  549.   IF com_installed THEN
  550.     error := 3
  551.   ELSE
  552.     IF (portnum < 1) OR (portnum > max_port) THEN
  553.       error := 1
  554.     ELSE
  555.       BEGIN
  556.  
  557.       {Set i/o addresses and other hardware specifics for selected port}
  558.  
  559.       uart_data := uart_base [portnum];
  560.       uart_ier  := uart_data + 1;
  561.       uart_iir  := uart_data + 2;
  562.       uart_lcr  := uart_data + 3;
  563.       uart_mcr  := uart_data + 4;
  564.       uart_lsr  := uart_data + 5;
  565.       uart_msr  := uart_data + 6;
  566.       uart_spr  := uart_data + 7;
  567.       intnum    := intnums [portnum];
  568.       i8259bit  := 1 SHL i8259levels [portnum];
  569.  
  570.       {Return error if hardware not installed}
  571.  
  572.       old_ier := Port [uart_ier];
  573.       Port [uart_ier] := 0;
  574.       IF Port [uart_ier] <> 0 THEN
  575.         error := 2
  576.       ELSE
  577.         BEGIN
  578.         error := 0;
  579.  
  580.         {Save original interrupt controller mask, then disable the
  581.         interrupt controller for this interrupt.}
  582.  
  583.         disable_interrupts;
  584.         old_i8259_mask := Port [$21];
  585.         Port [$21] := old_i8259_mask OR i8259bit;
  586.         enable_interrupts;
  587.  
  588.         {Clear the transmit and receive queues}
  589.  
  590.         com_flush_tx;
  591.         com_flush_rx;
  592.  
  593.         {Save current interrupt vector, then set the interrupt vector to
  594.         the address of our interrupt driver.}
  595.  
  596.         GetIntVec (intnum, old_vector);
  597.         SetIntVec (intnum, @com_interrupt_driver);
  598.         com_installed := True;
  599.  
  600.         {Set parity to none, turn off BREAK signal, and make sure
  601.         we're not addressing the baud rate registers.}
  602.  
  603.         Port [uart_lcr] := 3;
  604.  
  605.         {Save original contents of modem control register, then enable
  606.         interrupts to system bus and activate RTS.  Leave DTR the way
  607.         it was.}
  608.  
  609.         disable_interrupts;
  610.         old_mcr := Port [uart_mcr];
  611.         Port [uart_mcr] := $A OR (old_mcr AND 1);
  612.         enable_interrupts;
  613.  
  614.         {Enable interrupt on data-available.  The interrupt for
  615.         transmit-ready is enabled when a character is put into the
  616.         transmit queue, and disabled when the transmit queue is empty.}
  617.  
  618.         Port [uart_ier] := 1;
  619.  
  620.         {Enable the interrupt controller for this interrupt.}
  621.  
  622.         disable_interrupts;
  623.         Port [$21] := Port [$21] AND NOT i8259bit;
  624.         enable_interrupts;
  625.  
  626.         END;
  627.       END;
  628. END;
  629.  
  630.  
  631. {Deinstall the interrupt driver completely.  It doesn't change the baud rate
  632. or mess with DTR; it tries to leave the interrupt vectors and enables and
  633. everything else as it was when the driver was installed.
  634.  
  635. This procedure MUST be called by the exit procedure of this module before
  636. the program exits to DOS, or the interrupt driver will still
  637. be attached to its vector -- the next communications interrupt that came
  638. along would jump to the interrupt driver which is no longer protected and
  639. may have been written over.}
  640.  
  641.  
  642. PROCEDURE com_deinstall;
  643. BEGIN
  644.   IF com_installed THEN
  645.     BEGIN
  646.  
  647.     com_installed := False;
  648.  
  649.     {Restore Modem-Control-Register and Interrupt-Enable-Register.}
  650.  
  651.     Port [uart_mcr] := old_mcr;
  652.     Port [uart_ier] := old_ier;
  653.  
  654.     {Restore appropriate bit of interrupt controller's mask}
  655.  
  656.     disable_interrupts;
  657.     Port [$21] := Port [$21] AND NOT i8259bit OR
  658.      old_i8259_mask AND i8259bit;
  659.     enable_interrupts;
  660.  
  661.     {Reset the interrupt vector}
  662.  
  663.     SetIntVec (intnum, old_vector);
  664.  
  665.     END;
  666. END;
  667.  
  668.  
  669. {This procedure is called when the program exits for any reason.  It
  670. deinstalls the interrupt driver.}
  671.  
  672. {$F+} PROCEDURE exit_procedure; {$F-}
  673. BEGIN
  674.   com_deinstall;
  675.   ExitProc := exit_save;
  676. END;
  677.  
  678.  
  679. {This installs the exit procedure.}
  680.  
  681. BEGIN
  682.   exit_save := ExitProc;
  683.   ExitProc := @exit_procedure;
  684. END.
  685.